package com.gitee.generator.universal.common.jsonconfig;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import com.gitee.generator.universal.common.Constants;
import com.gitee.generator.universal.common.TreeNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.StringSubstitutor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 1.配置文件名称默认为config.json
 * 2.json字符串中使用 includes 属性引用其他文件
 * 3.json字符串中可以用el表达式引用配置，初次加载的时候会从文件中查找值，并且替换
 * 1.通过本类获取配置中的值（缓存中的对象不可以被操作，可以通过本类获得的值都是克隆过的）<br/>
 * 2.目前只有一个fastJson实现，后续需要的话会扩展其他的
 * @since 2020-08-19 11:24
 * @author wsx
 */
@Slf4j
public abstract class JsonConfig {

    /**小数点*/
    private static final String POINT = "\\.";
    /**数组参数数字校验*/
    static final String ARR_NUM_ERR = "数组%s第%d个参数应为数字类型";

    /** 引入子文件集合 "includes":["config-target.json"] */
    static final String INCLUDES = "includes";
    /**配置文件默认名称*/
    private static String configJsonName = "config.json";
    /**配置对象缓存*/
    static Object configCatch = null;
    /** 抽象类静态缓存 */
    private static JsonConfig configCache;
    /**获取配置对象单例*/
    Object getConfigCatch() {
        if (null == configCatch){
            reloadCache();
        }
        return configCatch;
    }
    /**
     * 修改配置文件名称
     * @param configName configName
     */
    public static void setConfigName(String configName){
        configJsonName =configName;
    }
    /**重新加载配置文件*/
    public static void reloadCache(){
        getInstance().reloadCacheInstance(configJsonName);
        log.debug("configCatch:{}", JSONUtil.formatJsonStr(toJsonStr(configCatch)));
    }
    /**
     * @return 实现类
     */
    private static JsonConfig getInstance() {
        if(null==configCache){
            configCache=new CreateByFastJson();
        }
        return configCache;
    }


    /**
     * 获取基本类型
     * @param arr 数组参数
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getBaseValue(String... arr) {
        return getInstance().getBaseValueInstance(arr);
    }
    /**
     * 获取基本类型
     * @param configStr 配置参数用 . 分割 a.b.c
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getBaseValue(String configStr) {
        if(null==configStr || configStr.trim().isEmpty()){
            return null;
        }
        return getBaseValue(configStr.split(POINT));
    }
    /**
     * 获取指定类型的对象
     * @param clazz 返回结果类型
     * @param arr 数组参数
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getObj(Class<T> clazz,String... arr) {
        Object obj=getBaseValue(arr);
        if(null==obj){
            return null;
        }
        return parse(getInstance().toJsonStrInstance(obj),clazz);
    }
    /**
     * 获取指定类型的对象
     * @param type 返回结果类型
     * @param arr 数组参数
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getObj(Type type,String... arr) {
        Object obj=getBaseValue(arr);
        if(null==obj){
            return null;
        }
        return parse(getInstance().toJsonStrInstance(obj),type);
    }
    /**
     * 获取指定类型的对象
     * @param arr 数组参数
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getObj(String... arr) {
        Object obj=getBaseValue(arr);
        if(null==obj){
            return null;
        }
        return parse(getInstance().toJsonStrInstance(obj));
    }
    /**
     * 获取指定类型的对象
     * @param clazz 返回结果类型
     * @param configStr 配置参数用 . 分割 a.b.c
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getObj(Class<T> clazz,String configStr) {
        if(null==configStr || configStr.trim().isEmpty()){
            return null;
        }
        return getObj(clazz,configStr.split(POINT));
    }
    /**
     * 获取指定类型的对象
     * @param type 返回结果类型
     * @param configStr 配置参数用 . 分割 a.b.c
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getObj(Type type,String configStr) {
        if(null==configStr || configStr.trim().isEmpty()){
            return null;
        }
        return getObj(type,configStr.split(POINT));
    }
    /**
     * 获取指定类型的对象
     * @param configStr 配置参数用 . 分割 a.b.c
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getObj(String configStr) {
        if(null==configStr || configStr.trim().isEmpty()){
            return null;
        }
        return getObj(configStr.split(POINT));
    }
    /**
     * 获取指定类型的List对象
     * @param clazz 返回结果类型
     * @param arr 数组参数
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> List<T> getList(Class<T> clazz, String... arr) {
        Object str=getBaseValue(arr);
        if(null==str){
            return new ArrayList<>();
        }
        return parseArray(getInstance().toJsonStrInstance(str),clazz);
    }
    /**
     * 获取指定类型的List对象
     * @param arr 数组参数
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> T getList(String... arr) {
        Object str=getBaseValue(arr);
        if(null==str){
            return null;
        }
        return parseArray(getInstance().toJsonStrInstance(str));
    }
    /**
     * 获取指定类型的List对象
     * @param clazz 返回结果类型
     * @param configStr 配置参数用 . 分割 a.b.c
     * @param <T> 返回结果类型
     * @return T
     */
    public static <T> List<T> getList(Class<T> clazz, String configStr) {
        if(null==configStr || configStr.trim().isEmpty()){
            return new ArrayList<>();
        }
        return getList(clazz,configStr.split(POINT));
    }
    /**
     * Json 转 对象
     * @param jsonStr Json 字符串
     * @param type 类型
     * @param <T> T
     * @return T
     */
    public static <T> T parse(String jsonStr,Type type) {
        return getInstance().parseInstance(jsonStr,type);
    }
    /**
     * Json 转 对象
     * @param jsonStr Json 字符串
     * @param <T> T
     * @return T
     */
    public static <T> T parse(String jsonStr) {
        return getInstance().parseInstance(jsonStr);
    }
    /**
     * Json 转 List对象
     * @param jsonStr Json 字符串
     * @param clazz 类型
     * @param <T> T
     * @return T
     */
    public static <T> List<T> parseArray(String jsonStr,Class<T> clazz) {
        return getInstance().parseArrayInstance(jsonStr,clazz);
    }
    /**
     * Json 转 List对象
     * @param jsonStr Json 字符串
     * @param <T> T
     * @return T
     */
    public static <T> T parseArray(String jsonStr) {
        return getInstance().parseArrayInstance(jsonStr);
    }
    /**
     * Object 转为 Json 字符串
     * @param obj obj
     * @return Json 字符串
     */
    public static String toJsonStr(Object obj){
        return getInstance().toJsonStrInstance(obj);
    }
    /**
     * 判断是否为json字符串
     * @param str json
     * @return boolean
     */
    public static boolean isJson(String str){
        return getInstance().isJsonInstance(str);
    }
    /**
     * 重新加载配置文件
     * @param fileName fileName
     */
    protected abstract void reloadCacheInstance(String fileName);
    /**
     * 获取基本类型，得到的对象是克隆的对象
     * @param arr 数组参数
     * @param <T> 返回结果类型
     * @return T
     */
    protected abstract <T> T getBaseValueInstance(String... arr);
    /**
     * 字符串转对象
     * @param jsonStr jsonStr
     * @param type type
     * @param <T> T
     * @return T
     */
    protected abstract <T> T parseInstance(String jsonStr,Type type);
    /**
     * 字符串转对象
     * @param jsonStr jsonStr
     * @param <T> T
     * @return T
     */
    protected abstract <T> T parseInstance(String jsonStr);
    /**
     * 字符串转List对象
     * @param jsonStr jsonStr
     * @param clazz 类型
     * @param <T> T
     * @return T
     */
    protected abstract <T> List<T> parseArrayInstance(String jsonStr, Class<T> clazz);
    /**
     * 字符串转对象
     * @param jsonStr jsonStr
     * @param <T> T
     * @return T
     */
    protected abstract <T> T parseArrayInstance(String jsonStr);
    /**
     * 判断是否为json字符串
     * @param str str
     * @return boolean
     */
    protected abstract boolean isJsonInstance(String str);
    /**
     * 对象转字符串
     * @param obj obj
     * @return 字符串
     */
    protected abstract String toJsonStrInstance(Object obj);
    /**
     * Util
     */
    public static class Util{
        /** 初始化 */
        private Util(){}
        /**el前缀*/
        private static final String EL_PREFIX = "${";
        /**el后缀*/
        private static final String EL_SUFFIX = "}";
        /**空字符串*/
        private static final String EMPTY = "";
        /**el表达式正则*/
        private static final Pattern PARA_DOLLAR = Pattern.compile("\\$\\{\\s*[^\\{^\\}]*\\s*\\}");
        /**循环引用错误*/
        private static final String CIRCULAR_REFERENCE_ERR = "不能出现循环引用：%s";
        /**
         * 构造引用链，检查循环引用
         * @param chains 链条
         * @param preNode 前一个节点
         * @param newNode 后一个节点
         */
        private static void addChainNode(List<StringBuilder> chains,String preNode,String newNode){
            if(null==chains){
                chains=new ArrayList<>();
            }
            boolean findTail=false;
            for (StringBuilder chain:chains) {
                if(chain.indexOf(newNode)==0){
                    chain.append("->").append(newNode);
                    throw ExceptionUtil.wrapRuntime(String.format(CIRCULAR_REFERENCE_ERR,chain));
                }
                if(chain.indexOf(preNode)==chain.length()-preNode.length()){
                    chain.append("->").append(newNode);
                    findTail=true;
                }
            }
            if(!findTail){
                chains.add(new StringBuilder(preNode).append("->").append(newNode));
            }
            log.debug("ChainNode:{}",chains);
        }
        /**替换配置文件中的表达式*/
        public static String replaceElStr(Map<String,?> objectMap){
            if(null==objectMap){
                return null;
            }
            String configJson= JsonConfig.toJsonStr(objectMap);
            List<StringBuilder> chains=new ArrayList<>();
            while (true){
                Matcher m = PARA_DOLLAR.matcher(configJson);
                int replaceNum=0;

                while (m.find()) {
                    String ms=m.group();
                    log.debug("find el:{}",ms);

                    String key = ms.replace(EL_PREFIX,EMPTY).replace(EL_SUFFIX,EMPTY).trim();
                    Object valueObj = null;
                    try {
                        List<String> keyArr= JsonConfig.parseArray(key,String.class);
                        if(null!=keyArr && !keyArr.isEmpty()){
                            valueObj = JsonConfig.getBaseValue(keyArr.toArray(new String[0]));
                        }
                    }catch (Exception e){
                        log.debug("key is not Array");
                    }
                    if(null==valueObj){
                        valueObj = JsonConfig.getBaseValue(key);
                    }
                    if(null==valueObj){
                        continue;
                    }
                    log.debug("need replace: {} -> {}",ms,valueObj);
                    /*json的value如果不是字符串类型，并且value的前后没有与其他字符串拼接，按照原来的类型替换*/
                    if(!(valueObj instanceof String) && !(valueObj instanceof Character)){
                        char prefix = configJson.charAt(m.start()-1);
                        char suffix = configJson.charAt(m.end());
                        if(prefix==suffix && (prefix=='"' || prefix=='\'')){
                            ms = prefix + ms + prefix;
                            log.debug("add prefix");
                        }
                    }
                    String value=valueObj.toString();
                    if(value.trim().length()>0){
                        log.debug("before replace:{}",configJson);
                        configJson=configJson.replace(ms,value);
                        m = PARA_DOLLAR.matcher(configJson);
                        log.debug("replace: {}->{} \nafter replace:{}",ms,value,configJson);
                        replaceNum++;
                    }
                    Matcher vm = PARA_DOLLAR.matcher(value);
                    while (vm.find()){
                        String vms=vm.group();
                        addChainNode(chains,ms,vms);
                    }
                }
                if(replaceNum==0 || !m.find()){
                    break;
                }
            }
            Object map = JsonConfig.parse(configJson);
            configJson = showTreeLoop(map);
            return configJson;
        }
        /** 遍历配置，从叶子节点向上查找其父级，尝试替换el表达式 */
        public static String showTreeLoop(Object canIt){
            if(canIt==null) {
                return null;
            }
            TreeNode treeNode =new TreeNode(canIt);
            Deque<TreeNode> deque = new ArrayDeque<>();
            deque.push(treeNode);
            while(!deque.isEmpty()){
                TreeNode treeNodeIn=deque.pop();
                Object node= treeNodeIn.getNodeData();
                //先往栈中压入右节点，再压左节点，这样出栈就是先左节点后右节点了。
                if(node instanceof Map<?,?>){
                    for(Map.Entry<?,?> en :((Map<?, ?>) node).entrySet()){
                        TreeNode t = new TreeNode(String.valueOf(en.getKey()),en.getValue(),treeNodeIn,null);
                        deque.push(t);
                    }
                }else if(node instanceof List<?>){
                    List<?> list = (List<?>)node;
                    for(int i=0;i<list.size();i++){
                        TreeNode t = new TreeNode(String.valueOf(i),list.get(i),treeNodeIn,null);
                        deque.push(t);
                    }
                }else {
                    /* 遍历到叶子节点之后 */
                    StringJoiner nameSj=new StringJoiner(Constants.COMMA);
                    nameSj.add(node.toString());

                    List<TreeNode> leafToRootChain = CollUtil.newArrayList(treeNodeIn);
                    leafToRootChain.addAll(treeNodeIn.getElders());

                    /* 自下而上逐层遍历 */
                    for(TreeNode elNode:leafToRootChain){
                        nameSj.add(elNode.getNodeName());
                        Object elNodeData = elNode.getNodeData();
                        String elNodeStr = elNodeData.toString();
                        /* 如果属性是包含el表达式的字符串，遍历其所有父级，尝试替换el表达式 */
                        if((elNodeData instanceof String) && haveElStr(elNodeStr)){
                            if(null==elNode.getParentNode() || !(elNode.getParentNode().getNodeData() instanceof Map)){
                                continue;
                            }
                            Map<String,Object> fatherNode = (Map<String, Object>) elNode.getParentNode().getNodeData();
                            /* 自下而上遍历节点的所有父级，尝试替换el表达式 */
                            for(TreeNode anParentNode : elNode.getElders()){
                                if((anParentNode.getNodeData() instanceof Map)){
                                    String tag = new StringSubstitutor((Map<String,Object>) anParentNode.getNodeData()).replace(elNodeStr);
                                    if(!elNodeStr.equals(tag)){
                                        log.debug("replace str from:{}",elNodeStr);
                                        log.debug("replace str --to:{}",tag);
                                        fatherNode.put(elNode.getNodeName(),tag);
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    log.info("--- kye str chain --> {}",nameSj);
                }
            }
            return JsonConfig.toJsonStr(canIt);
        }

        /**
         * 判断字符串是否有el表达式
         * @param str 字符串参数
         * @return boolean
         */
        public static boolean haveElStr(String str){
            boolean have = CharSequenceUtil.isNotBlank(str) && PARA_DOLLAR.matcher(str).find();
            if(have){
                log.debug("haveElStr:{}",str);
            }
            return have;
        }
        /**
         * 读取文件字符串
         * Thread.currentThread().getContextClassLoader().getResource(resourceFileName).getPath();
         * @param resourceFileName resourceFileName
         * @return 字符串
         */
        public static String readResourceFileContent(String resourceFileName) {
            String classPath = JsonConfig.class.getClassLoader().getResource("").getPath();
            String fileAbsolutePath= classPath+resourceFileName;
            log.info("fileAbsolutePath:{}",fileAbsolutePath);
            String configStr = FileUtil.readUtf8String(fileAbsolutePath);
            log.debug("JsonConfig read:{} content:{}",resourceFileName,configStr);
            return configStr;
        }
        /**
         * 通过序列化的方式克隆对象
         * @param obj obj
         * @return 克隆后的对象
         */
        @SuppressWarnings("unchecked")
        public static <T extends Serializable> T clone(T obj) {
            if (null == obj){return null;}
            T clonedObj = null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(obj);
                oos.close();

                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bais);
                clonedObj = (T) ois.readObject();
                ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return clonedObj;
        }
    }
}